/*
 * Copyright (C) 2005 - 2013 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008 - 2013 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010 - 2013 ProjectSkyfire <http://www.projectskyfire.org/>
 *
 * Copyright (C) 2011 - 2013 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef ARKCORE_LOOTMGR_H
#define ARKCORE_LOOTMGR_H

#include "ItemEnchantmentMgr.h"
#include "ByteBuffer.h"
#include "RefManager.h"
#include "SharedDefines.h"
#include "ConditionMgr.h"

#include <map>
#include <vector>

enum RollType
{
    ROLL_PASS = 0, ROLL_NEED = 1, ROLL_GREED = 2, ROLL_DISENCHANT = 3, MAX_ROLL_TYPE = 4
};

enum RollMask
{
    ROLL_FLAG_TYPE_PASS = 0x01, ROLL_FLAG_TYPE_NEED = 0x02, ROLL_FLAG_TYPE_GREED = 0x04, ROLL_FLAG_TYPE_DISENCHANT = 0x08,

    ROLL_ALL_TYPE_NO_DISENCHANT = 0x07, ROLL_ALL_TYPE_MASK = 0x0F
};

#define MAX_NR_LOOT_ITEMS 16
// note: the client cannot show more than 16 items total
#define MAX_NR_QUEST_ITEMS 32
// unrelated to the number of quest items shown, just for reserve

enum LootMethod
{
    FREE_FOR_ALL = 0, ROUND_ROBIN = 1, MASTER_LOOT = 2, GROUP_LOOT = 3, NEED_BEFORE_GREED = 4
};

enum PermissionTypes
{
    ALL_PERMISSION = 0, GROUP_PERMISSION = 1, MASTER_PERMISSION = 2, ROUND_ROBIN_PERMISSION = 3, OWNER_PERMISSION = 4, NONE_PERMISSION = 5,
};

enum LootType
{
    LOOT_CORPSE = 1, LOOT_PICKPOCKETING = 2, LOOT_FISHING = 3, LOOT_DISENCHANTING = 4,
    // ignored always by client
    LOOT_SKINNING = 6, LOOT_PROSPECTING = 7, LOOT_MILLING = 8,

    LOOT_FISHINGHOLE = 20,          // unsupported by client, sending LOOT_FISHING instead
    LOOT_INSIGNIA = 21
// unsupported by client, sending LOOT_CORPSE instead
};

// type of Loot Item in Loot View
enum LootSlotType
{
    LOOT_SLOT_TYPE_ALLOW_LOOT = 0,          // player can loot the item.
    LOOT_SLOT_TYPE_ROLL_ONGOING = 1,          // roll is ongoing. player cannot loot.
    LOOT_SLOT_TYPE_MASTER = 2,          // item can only be distributed by group loot master.
    LOOT_SLOT_TYPE_LOCKED = 3,          // item is shown in red. player cannot loot.
    LOOT_SLOT_TYPE_OWNER = 4,
// ignore binding confirmation and etc, for single player looting
};

class Player;
class LootStore;
class ConditionMgr;

struct LootStoreItem
{
    uint32 itemid;          // id of the item
    float chance;          // always positive, chance to drop for both quest and non-quest items, chance to be used for refs
    int32 mincountOrRef;          // mincount for drop items (positive) or minus referenced TemplateleId (negative)
    uint16 lootmode;
    uint8 group :7;
    bool needs_quest :1;          // quest drop (negative ChanceOrQuestChance in DB)
    uint8 maxcount :8;          // max drop count for the item (mincountOrRef positive) or Ref multiplicator (mincountOrRef negative)
    ConditionList conditions;          // additional loot condition

    // Constructor, converting ChanceOrQuestChance -> (chance, needs_quest)
    // displayid is filled in IsValid() which must be called after
    LootStoreItem (uint32 _itemid, float _chanceOrQuestChance, uint16 _lootmode, uint8 _group, int32 _mincountOrRef, uint8 _maxcount) :
            itemid(_itemid), chance(fabs(_chanceOrQuestChance)), mincountOrRef(_mincountOrRef), lootmode(_lootmode), group(_group), needs_quest(_chanceOrQuestChance < 0), maxcount(_maxcount)
    {
    }

    bool Roll (bool rate) const;          // Checks if the entry takes it's chance (at loot generation)
    bool IsValid (LootStore const& store, uint32 entry) const;
    // Checks correctness of values
};

typedef std::set<uint32> AllowedLooterSet;

struct LootItem
{
    uint32 itemid;
    uint32 randomSuffix;
    int32 randomPropertyId;
    ConditionList conditions;          // additional loot condition
    AllowedLooterSet allowedGUIDs;
    uint8 count :8;
    bool is_looted :1;
    bool is_blocked :1;
    bool freeforall :1;          // free for all
    bool is_underthreshold :1;
    bool is_counted :1;
    bool needs_quest :1;          // quest drop

    // Constructor, copies most fields from LootStoreItem, generates random count and random suffixes/properties
    // Should be called for non-reference LootStoreItem entries only (mincountOrRef > 0)
    explicit LootItem (LootStoreItem const& li);

    // Basic checks for player/item compatibility - if false no chance to see the item in the loot
    bool AllowedForPlayer (Player const * player) const;

    void AddAllowedLooter (Player const* player);
    AllowedLooterSet* GetAllowedLooters ()
    {
        return &allowedGUIDs;
    }
};

struct QuestItem
{
    uint8 index;          // position in quest_items;
    bool is_looted;

    QuestItem () :
            index(0), is_looted(false)
    {
    }

    QuestItem (uint8 _index, bool _islooted = false) :
            index(_index), is_looted(_islooted)
    {
    }
};

struct Loot;
class LootTemplate;

typedef std::vector<QuestItem> QuestItemList;
typedef std::vector<LootItem> LootItemList;
typedef std::map<uint32, QuestItemList *> QuestItemMap;
typedef std::vector<LootStoreItem> LootStoreItemList;
typedef UNORDERED_MAP<uint32, LootTemplate*> LootTemplateMap;

typedef std::set<uint32> LootIdSet;

class LootStore
{
public:
    explicit LootStore (char const* name, char const* entryName, bool ratesAllowed) :
            m_name(name), m_entryName(entryName), m_ratesAllowed(ratesAllowed)
    {
    }

    virtual ~LootStore ()
    {
        Clear();
    }

    void Verify () const;

    uint32 LoadAndCollectLootIds (LootIdSet& ids_set);
    void CheckLootRefs (LootIdSet* ref_set = NULL) const;          // check existence reference and remove it from ref_set
    void ReportUnusedIds (LootIdSet const& ids_set) const;
    void ReportNotExistedId (uint32 id) const;

    bool HaveLootFor (uint32 loot_id) const
    {
        return m_LootTemplates.find(loot_id) != m_LootTemplates.end();
    }
    bool HaveQuestLootFor (uint32 loot_id) const;
    bool HaveQuestLootForPlayer (uint32 loot_id, Player* player) const;

    LootTemplate const* GetLootFor (uint32 loot_id) const;
    void ResetConditions ();
    LootTemplate* GetLootForConditionFill (uint32 loot_id);

    char const* GetName () const
    {
        return m_name;
    }
    char const* GetEntryName () const
    {
        return m_entryName;
    }
    bool IsRatesAllowed () const
    {
        return m_ratesAllowed;
    }
protected:
    uint32 LoadLootTable ();
    void Clear ();
private:
    LootTemplateMap m_LootTemplates;
    char const* m_name;
    char const* m_entryName;
    bool m_ratesAllowed;
};

class LootTemplate
{
    class LootGroup;          // A set of loot definitions for items (refs are not allowed inside)
    typedef std::vector<LootGroup> LootGroups;

public:
    // Adds an entry to the group (at loading stage)
    void AddEntry (LootStoreItem& item);
    // Rolls for every item in the template and adds the rolled items the the loot
    void Process (Loot& loot, bool rate, uint16 lootMode, uint8 groupId = 0) const;
    void CopyConditions (ConditionList conditions);

    // True if template includes at least 1 quest drop entry
    bool HasQuestDrop (LootTemplateMap const& store, uint8 groupId = 0) const;
    // True if template includes at least 1 quest drop for an active quest of the player
    bool HasQuestDropForPlayer (LootTemplateMap const& store, Player const * player, uint8 groupId = 0) const;

    // Checks integrity of the template
    void Verify (LootStore const& store, uint32 Id) const;
    void CheckLootRefs (LootTemplateMap const& store, LootIdSet* ref_set) const;
    bool addConditionItem (Condition* cond);
    bool isReference (uint32 id);

private:
    LootStoreItemList Entries;          // not grouped only
    LootGroups Groups;          // groups have own (optimised) processing, grouped entries go there
};

//=====================================================

class LootValidatorRef: public Reference<Loot, LootValidatorRef>
{
public:
    LootValidatorRef ()
    {
    }
    void targetObjectDestroyLink ()
    {
    }
    void sourceObjectDestroyLink ()
    {
    }
};

//=====================================================

class LootValidatorRefManager: public RefManager<Loot, LootValidatorRef>
{
public:
    typedef LinkedListHead::Iterator<LootValidatorRef> iterator;

    LootValidatorRef* getFirst ()
    {
        return (LootValidatorRef*) RefManager<Loot, LootValidatorRef>::getFirst();
    }
    LootValidatorRef* getLast ()
    {
        return (LootValidatorRef*) RefManager<Loot, LootValidatorRef>::getLast();
    }

    iterator begin ()
    {
        return iterator(getFirst());
    }
    iterator end ()
    {
        return iterator(NULL);
    }
    iterator rbegin ()
    {
        return iterator(getLast());
    }
    iterator rend ()
    {
        return iterator(NULL);
    }
};

//=====================================================
struct LootView;

ByteBuffer& operator<< (ByteBuffer& b, LootItem const& li);
ByteBuffer& operator<< (ByteBuffer& b, LootView const& lv);

struct Loot
{
    friend ByteBuffer& operator<< (ByteBuffer& b, LootView const& lv);

    QuestItemMap const& GetPlayerQuestItems () const
    {
        return PlayerQuestItems;
    }
    QuestItemMap const& GetPlayerFFAItems () const
    {
        return PlayerFFAItems;
    }
    QuestItemMap const& GetPlayerNonQuestNonFFAConditionalItems () const
    {
        return PlayerNonQuestNonFFAConditionalItems;
    }

    std::vector<LootItem> items;
    uint32 gold;
    uint8 unlootedCount;
    uint64 roundRobinPlayer;          // GUID of the player having the Round-Robin ownership for the loot. If 0, round robin owner has released.
    LootType loot_type;          // required for achievement system

    Loot (uint32 _gold = 0) :
            gold(_gold), unlootedCount(0), loot_type(LOOT_CORPSE)
    {
    }
    ~Loot ()
    {
        clear();
    }

    // if loot becomes invalid this reference is used to inform the listener
    void addLootValidatorRef (LootValidatorRef* pLootValidatorRef)
    {
        i_LootValidatorRefManager.insertFirst(pLootValidatorRef);
    }

    // void clear();
    void clear ()
    {
        for (QuestItemMap::const_iterator itr = PlayerQuestItems.begin(); itr != PlayerQuestItems.end(); ++itr)
            delete itr->second;
        PlayerQuestItems.clear();

        for (QuestItemMap::const_iterator itr = PlayerFFAItems.begin(); itr != PlayerFFAItems.end(); ++itr)
            delete itr->second;
        PlayerFFAItems.clear();

        for (QuestItemMap::const_iterator itr = PlayerNonQuestNonFFAConditionalItems.begin(); itr != PlayerNonQuestNonFFAConditionalItems.end(); ++itr)
            delete itr->second;
        PlayerNonQuestNonFFAConditionalItems.clear();

        PlayersLooting.clear();
        items.clear();
        quest_items.clear();
        gold = 0;
        unlootedCount = 0;
        roundRobinPlayer = 0;
        i_LootValidatorRefManager.clearReferences();
    }

    bool empty () const
    {
        return items.empty() && gold == 0;
    }
    bool isLooted () const
    {
        return gold == 0 && unlootedCount == 0;
    }

    void NotifyItemRemoved (uint8 lootIndex);
    void NotifyQuestItemRemoved (uint8 questIndex);
    void NotifyMoneyRemoved ();
    void AddLooter (uint64 GUID)
    {
        PlayersLooting.insert(GUID);
    }
    void RemoveLooter (uint64 GUID)
    {
        PlayersLooting.erase(GUID);
    }

    void generateMoneyLoot (uint32 minAmount, uint32 maxAmount);
    bool FillLoot (uint32 lootId, LootStore const& store, Player* lootOwner, bool personal, bool noEmptyError = false, uint16 lootMode = LOOT_MODE_DEFAULT);

    // Inserts the item into the loot (called by LootTemplate processors)
    void AddItem (LootStoreItem const & item);

    LootItem* LootItemInSlot (uint32 lootslot, Player* player, QuestItem** qitem = NULL, QuestItem** ffaitem = NULL, QuestItem** conditem = NULL);
    uint32 GetMaxSlotInLootFor (Player* player) const;
    bool hasItemFor (Player* player) const;
    bool hasOverThresholdItem () const;

private:
    void FillNotNormalLootFor (Player* player, bool withCurrency);
    QuestItemList* FillFFALoot (Player* player);
    QuestItemList* FillQuestLoot (Player* player);
    QuestItemList* FillNonQuestNonFFAConditionalLoot (Player* player);

    std::vector<LootItem> quest_items;
    std::set<uint64> PlayersLooting;
    QuestItemMap PlayerQuestItems;
    QuestItemMap PlayerFFAItems;
    QuestItemMap PlayerNonQuestNonFFAConditionalItems;

    // All rolls are registered here. They need to know, when the loot is not valid anymore
    LootValidatorRefManager i_LootValidatorRefManager;
};

struct LootView
{
    Loot &loot;
    Player *viewer;
    PermissionTypes permission;
    LootView (Loot &_loot, Player *_viewer, PermissionTypes _permission = ALL_PERMISSION) :
            loot(_loot), viewer(_viewer), permission(_permission)
    {
    }
};

extern LootStore LootTemplates_Creature;
extern LootStore LootTemplates_Fishing;
extern LootStore LootTemplates_Gameobject;
extern LootStore LootTemplates_Item;
extern LootStore LootTemplates_Mail;
extern LootStore LootTemplates_Milling;
extern LootStore LootTemplates_Pickpocketing;
extern LootStore LootTemplates_Reference;
extern LootStore LootTemplates_Skinning;
extern LootStore LootTemplates_Disenchant;
extern LootStore LootTemplates_Prospecting;
extern LootStore LootTemplates_Spell;

void LoadLootTemplates_Creature ();
void LoadLootTemplates_Fishing ();
void LoadLootTemplates_Gameobject ();
void LoadLootTemplates_Item ();
void LoadLootTemplates_Mail ();
void LoadLootTemplates_Milling ();
void LoadLootTemplates_Pickpocketing ();
void LoadLootTemplates_Skinning ();
void LoadLootTemplates_Disenchant ();
void LoadLootTemplates_Prospecting ();

void LoadLootTemplates_Spell ();
void LoadLootTemplates_Reference ();

inline void LoadLootTables ()
{
    LoadLootTemplates_Creature();
    LoadLootTemplates_Fishing();
    LoadLootTemplates_Gameobject();
    LoadLootTemplates_Item();
    LoadLootTemplates_Mail();
    LoadLootTemplates_Milling();
    LoadLootTemplates_Pickpocketing();
    LoadLootTemplates_Skinning();
    LoadLootTemplates_Disenchant();
    LoadLootTemplates_Prospecting();
    LoadLootTemplates_Spell();

    LoadLootTemplates_Reference();
}

#endif
